package com.econage.eccm.test.base.webdatasource.facade;

import com.econage.eccm.test.base.webdatasource.DataSourceConfiguration;
import com.econage.eccm.test.base.webdatasource.web.AppDataSourceContext;
import com.google.common.base.MoreObjects;
import org.apache.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;

/*
* 包装类
* 1，保证连接只会被一个线程调用，禁止多线程及跨线程调用
* 2，处理若干基本方法
* 3，拦截若干方法
* 4，包装回收方法，语句创建方法
* */
public class ConnectionFacade extends JdbcFacade {
    private static final Logger log = Logger.getLogger(ConnectionFacade.class);

    /**
     * {@link java.sql.Connection#close()} method name
     */
    public static final String CLOSE_VAL = "close";
    /**
     * {@link java.sql.Connection#isClosed()} method name
     */
    public static final String ISCLOSED_VAL = "isClosed";
    /**
     * {@link java.sql.Connection#isValid(int)} method name
     */
    public static final String ISVALID_VAL = "isValid";


    public static final String CREATE_STATEMENT      = "createStatement";
    public static final String PREPARE_STATEMENT     = "prepareStatement";
    public static final String PREPARE_CALL          = "prepareCall";
    public static final String[] STATEMENT_TYPES = {CREATE_STATEMENT, PREPARE_STATEMENT, PREPARE_CALL};

    public static final String SETCOMMIT_VAL 	           = "commit";
    public static final String SETAUTOCOMMIT_VAL 	       = "setAutoCommit";
    public static final String SETTRANSACTIONISOLATION_VAL = "setTransactionIsolation";

    private java.sql.Connection connection;//实际操作的连接对象

    private Thread ownerThread;//创建是，所属的线程
    private DataSourceConfiguration configuration;
    private AppDataSourceContext appDataSourceContext;

    private List<StatementEntry> createdStatements = new LinkedList<StatementEntry>();//创建后，未关闭的语句

    public ConnectionFacade(
            Connection connection,
            Thread ownerThread,
            DataSourceConfiguration configuration,
            AppDataSourceContext appDataSourceContext)
    {
        this.connection = connection;
        this.ownerThread = ownerThread;
        this.configuration = configuration;
        this.appDataSourceContext = appDataSourceContext;
    }

    @Override
    public Object doInvoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(ownerThread!=Thread.currentThread()){
            throw new SQLException("当前执行线程不属于创建线程,可能是新建了一个线程,出现了跨线程调用连接的情况!");
        }


        //需要拒绝调用的场景
        if (connection==null) {
            //已经关闭
            if (compare(ISCLOSED_VAL, method)) {
                return Boolean.TRUE;
            }
            else if (compare(CLOSE_VAL, method)) {
                return null;
            }
            else if (compare(ISVALID_VAL, method)) {
                return Boolean.FALSE;
            }
            else if(compare(SETCOMMIT_VAL,method)){
                return null;
            }
            else{
                throw new SQLException("Connection closed.");
            }
        }else if(compare(SETTRANSACTIONISOLATION_VAL,method)){
            //生产环境不接受隔离等级变更
            if(configuration.isIgnoreTransactionChange()){
                return null;
            }
        }else if(compare(SETAUTOCOMMIT_VAL,method)){
            //生产环境不接受提交变更
            if(configuration.isIgnoreCommitChange()){
                return null;
            }
        }

        //需要特殊处理的操作
        if(compare(CLOSE_VAL, method)){
            //如果不是强制关闭操作，则忽略
            if(!appDataSourceContext.isRunningCloseConnectionForce()){
                return null;
            }
            doClose();
            return null;
        }else if(process(STATEMENT_TYPES, method)){
            //创建语句，则返回一个包装类
            return createStatement(proxy,method,args);
        }

        //其他操作
        return method.invoke(connection,args);
    }

    private Object createStatement(Object connectionProxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
        Object statement = method.invoke(connection,args);

        if (compare(CREATE_STATEMENT,method)) {
            //createStatement
            Statement statementFacade = configuration.newStatementFacade((Statement) statement,(Connection) connectionProxy);
            createdStatements.add(new StatementEntry(statementFacade));
            return statementFacade;
        }else if (compare(PREPARE_STATEMENT,method)) {
            //prepareStatement
            String sql = (String)args[0];
            PreparedStatement statementFacade = configuration.newPreparedStatementFacade((PreparedStatement) statement,(Connection) connectionProxy,sql);
            createdStatements.add(new StatementEntry(statementFacade));
            return statementFacade;
        }else if (compare(PREPARE_CALL,method)) {
            //prepareCall
            String sql = (String)args[0];
            CallableStatement statementFacade = configuration.newCallableStatementFacade((CallableStatement) statement,(Connection) connectionProxy,sql);
            createdStatements.add(new StatementEntry(statementFacade));
            return statementFacade;
        }else {
            return statement;
        }

    }

    private void doClose(){
        if(connection==null){
            return;
        }
        try{
            //回收语句
            while (createdStatements.size()>0) {
                StatementEntry ws = createdStatements.remove(0);
                Statement st = ws.getStatement();
                boolean shallClose = false;
                try {
                    shallClose = st!=null && (!st.isClosed());
                    if (shallClose) {
                        st.close();
                    }
                } catch (Exception ignore) {
                    if (log.isDebugEnabled()) {
                        log.debug("Unable to closed statement upon connection close.",ignore);
                    }
                } finally {
                    if (shallClose&&configuration.isStatementLeakWaring()) {
                        log.warn("Statement created, but was not closed at:", ws.getAllocationStack());
                    }
                }
            }
        }finally{
            //调用底层资源，可能会报错，finall块确保内部状态正常回收
            try {
                this.connection.close();
            }catch (SQLException ignore) {
            }finally{
                this.connection = null;
            }
        }
    }

    protected class StatementEntry {
        private Statement statement;
        private Throwable allocationStack;

        public StatementEntry(Statement statement) {
            this.statement = statement;
            if (ConnectionFacade.this.configuration.isTraceStatementCreation()) {
                this.allocationStack = new Throwable();
            }
        }

        public Statement getStatement() {
            return statement;
        }

        public Throwable getAllocationStack() {
            return allocationStack;
        }
    }


    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .omitNullValues()
                .add("ownerThread", ownerThread)
                .add("connection",connection)
                .toString();
    }


}
